package com.zoro.barn.cloud.gateway.loadbalance;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.zoro.barn.commons.tools.json.JsonUtil;
import com.zoro.barn.commons.webcommons.response.entity.BarnResponseEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;


/**
 * @author zhaoxingwu
 */
public class WebClientComponent {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final WebClient.Builder builder;

    public WebClientComponent(WebClient.Builder builder) {
        this.builder = builder;
    }

    public <T> Mono<T> post(String url, Object data, Class<T> tClass) {
        return this.logRequest(
                this.preparePost(url,data).bodyToMono(tClass), url, data);
    }

    public <T> Mono<T> post(String url, Object data, ParameterizedTypeReference<T> tClass) {

        return this.logRequest(
                this.preparePost(url,data).bodyToMono(tClass), url, data);

    }

    /**
     * 输出请求日志
     * @param req 请求
     * @param url 请求地址
     * @param data 请求数据
     * @return 响应
     * @param <T> T
     */
    private <T> Mono<T> logRequest(Mono<T> req, String url, Object data){
        RequestTimeRecorder recorder = new RequestTimeRecorder();
        return req
                .doOnRequest(value -> {
                    try {
                        if (logger.isDebugEnabled()) {
                            logger.debug("[{}] on request: url：{}, param: {}", recorder.id, url, JsonUtil.toString(data));
                        }
                    } catch (JsonProcessingException e) {
                        logger.error("[{}] can not translate param object to json string,{}", recorder.id, data, e);
                    }
                    recorder.start = System.currentTimeMillis();
                })
                .doOnNext(v -> {
                    if (logger.isDebugEnabled()) {
                        long use = recorder.use();
                        // TODO 这里的v是响应值，可能会数据过大对内存造成影响
                        logger.debug("[{}] on next: {}, use: {}", recorder.id, v, use);
                    }
                });
    }

    private WebClient.ResponseSpec preparePost(String url, Object data){
        return this.builder
                .build()
                .post()
                .uri(url)
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(data)
                .retrieve();

    }

    public <T> Mono<T> postUnwrap(String url, Object data, Class<T> tClass){

        if (BarnResponseEntity.class.isAssignableFrom(tClass)){
            return this.post(url, data, tClass);
        } else {
            // 这里需要把tClass 包装一下，再传进去,再将响应值解包
            return this.post(url, data, ParameterizedTypeReference.<BarnResponseEntity<T>>forType(new ParameterizedType() {
                @Override
                public Type[] getActualTypeArguments() {
                    return new Type[]{tClass};
                }

                @Override
                public Type getRawType() {
                    return BarnResponseEntity.class;
                }

                @Override
                public Type getOwnerType() {
                    return null;
                }
            })).map(BarnResponseEntity::getData);
        }
    }

    private static class RequestTimeRecorder {
        private long start = 0;
        /**
         * 这个用于标记同一次请求，因为 WebClient 对于一次请求的整个过程可能不在同一个线程内
         */
        private final String id;

        public RequestTimeRecorder() {
            this.id = ObjectUtils.getIdentityHexString(this);
        }

        private long use() {
            return System.currentTimeMillis() - start;
        }
    }
}
